/*
 * This file is part of b.AMP.
 *
 * b.AMP is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * b.AMP is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with b.AMP.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "addon.hpp"
#include "control.hpp"
#include "control_config.hpp"
#include "control_msg_wrap.hpp"
#include "logger.hpp"

#include "pipeline/plugin_loader.hpp"
#include "service/message_loop.hpp"
#include "service/service.hpp"

#include <boost/filesystem.hpp>
#include <boost/foreach.hpp>

#include <memory>
#include <string>

namespace fs = boost::filesystem;

/*
 * Implementation.
 */
struct bamp::Control::Implementation
{
    Implementation(bamp::Service& svc, bamp::PluginLoader& pl)
        : m_svc(svc), m_plugins(pl) {}
    
    // Message handler prototypes
    BAMP_CONTROL_MSG_DECLARE_ALL;

    // Internal functions
    void init(const std::string& configfile);
    void cleanup();
    void createParent(const fs::path path);
    void loadAddOns(const fs::path& path);
    void loadAddOn(const fs::path& path);

    // Internal data
    std::vector<Addon> m_addons;
    ControlConfig      m_config;
    fs::path           m_configfile;

    // Parent service object
    bamp::Service&    m_svc;

    // System-wide plugin loader
    bamp::PluginLoader& m_plugins;
};


/*
 * Public interface.
 */

bamp::Control::Control(bamp::PluginLoader& pl,
                       const std::string& configfile)
    : pimpl(new Implementation(*this, pl))
{
    // Initialize internals
    pimpl->init(configfile);
    
    // Connect all message handlers
    BAMP_CONTROL_MSG_CONNECT_ALL
        (thread(), bamp::Control::Implementation, *pimpl);

    BAMP_LOG_TRACE("control subsystem initialized");
}

bamp::Control::~Control()
{
    // Nothing here
}


/*
 * Internal functions.
 */

void bamp::Control::Implementation::init
(const std::string& configfile)
{
    // Load config file if it exists. Otherwise, just use default
    // values.
    m_configfile = fs::path(ControlConfig::replaceEnvVar(configfile));
    if (fs::exists(m_configfile))
    {
        BAMP_LOG_TRACE("loading config from " + m_configfile.string());
        m_config.load(m_configfile);
    }

    // Set logging file. Create its parent directory if it does not
    // already exist.
    {
        const fs::path logfile(m_config.logfile);
        createParent(logfile);
        BAMP_LOG_TRACE("logging to " + logfile.string());
        Logger::setFile(logfile);
    }

    // Load all add-ons
    BOOST_FOREACH(const fs::path& path, m_config.path)
    {
        BAMP_LOG_TRACE("loading add-ons from " + path.string());
        loadAddOns(path);
    }
}

void bamp::Control::Implementation::cleanup()
{
    createParent(m_configfile);
    m_config.save(m_configfile);
}

void bamp::Control::Implementation::createParent(const fs::path path)
{
    const fs::path parent (path.parent_path());
    if (!parent.empty() && !fs::exists(parent))
    {
        BAMP_LOG_TRACE("creating directory " + parent.string());
        fs::create_directory(parent);
    }    
}

void bamp::Control::Implementation::loadAddOns(const fs::path& path)
{
    // Make sure the path exists and is a directory so we can list its
    // contents
    if (!fs::exists(path))
    {
        BAMP_LOG_ERROR(path.string() + std::string(": directory does not exist"));
        return;
    }
    if (!fs::is_directory(path))
    {
        BAMP_LOG_ERROR(path.string() + ": not a directory");
        return;
    }

    // Look for subdirectories: they should contain add-ons to load
    fs::directory_iterator end;
    for (fs::directory_iterator it(path); it != end; ++it)
    {
        try {
            if (fs::is_directory(it->status()))
                loadAddOn(it->path());
        } catch (const std::exception& e) {
            BAMP_LOG_ERROR(it->path().filename().string() + ": " + e.what());
        }
    }
}

void bamp::Control::Implementation::loadAddOn(const fs::path& path)
{
    BAMP_LOG_TRACE("loading add-on " + path.filename().string());

    // Load add-on descriptor
    Addon addon;
    addon.load(path);

    // Load all plugins
    BOOST_FOREACH(const fs::path& plugin, addon.plugins)
    {
        BAMP_LOG_TRACE("loading plugin: " + plugin.string());
        try {
            m_plugins.load(path/plugin, 0);
        } catch (const std::exception& e) {
            BAMP_LOG_ERROR(plugin.filename().string() +
                           ": could not load plugin: " + 
                           e.what());
        }
    }

    // Load all extensions
    // TODO
}


/*
 * Messaging interface.
 */

void bamp::Control::Implementation::installAddOn(MessagePtr msg)
{
    
}

void bamp::Control::Implementation::uninstallAddOn(MessagePtr msg)
{

}

void bamp::Control::Implementation::listAddOns(MessagePtr msg)
{

}

void bamp::Control::Implementation::enableAddOn(MessagePtr msg)
{

}

void bamp::Control::Implementation::disableAddOn(MessagePtr msg)
{

}

void bamp::Control::Implementation::getAddOnInfo(MessagePtr msg)
{

}

void bamp::Control::Implementation::getConfig(MessagePtr msg)
{

}

void bamp::Control::Implementation::setConfig(MessagePtr msg)
{

}

void bamp::Control::Implementation::exit(MessagePtr msg)
{
    cleanup();
    m_svc.stop();
    BAMP_LOG_TRACE("control subsystem stopping");
}
